#include "GenerateByteCode.h"

#include "ScriptCompiler/Description/NamespaceDescription.h"
#include "ScriptCompiler/Description/TypeDescription.h"
#include "ScriptCompiler/Description/FunctionDescription.h"
#include "ScriptCompiler/Description/FieldDescription.h"
#include "ScriptCompiler/Description/VariableDescription.h"
#include "ScriptCompiler/Description/ParameterDescription.h"
#include "ScriptCompiler/Description/DecoratedTypeDescription.h"
#include "ScriptCompiler/Description/FunctionBodyDescription.h"
#include "ScriptCompiler/Description/DecoratorDescription.h"
#include "ScriptCompiler/Description/ConstructorDescription.h"

#include "ScriptCompiler/FrameOperation/FrameOperation.h"
#include "ScriptCompiler/FrameOperation/LoadFieldOperation.h"
#include "ScriptCompiler/FrameOperation/LoadFieldAddressOperation.h"
#include "ScriptCompiler/FrameOperation/LoadVariableOperation.h"
#include "ScriptCompiler/FrameOperation/LoadVariableAddressOperation.h"
#include "ScriptCompiler/FrameOperation/StoreFieldOperation.h"
#include "ScriptCompiler/FrameOperation/StoreVariableOperation.h"
#include "ScriptCompiler/FrameOperation/LoadLiteralOperation.h"
#include "ScriptCompiler/FrameOperation/FunctionCallOperation.h"
#include "ScriptCompiler/FrameOperation/ConstructorCallOperation.h"
#include "ScriptCompiler/FrameOperation/LoadArgumentOperation.h"
#include "ScriptCompiler/FrameOperation/LoadArgumentAddressOperation.h"
#include "ScriptCompiler/FrameOperation/StoreArgumentOperation.h"
#include "ScriptCompiler/FrameOperation/BoxInnerTypeOperation.h"
#include "ScriptCompiler/FrameOperation/NewObjectOperation.h"
#include "ScriptCompiler/FrameOperation/VirtualFunctionCallOperation.h"
#include "ScriptCompiler/FrameOperation/JumpOperation.h"
#include "ScriptCompiler/FrameOperation/SimpleInstructionOperation.h"
#include "ScriptCompiler/FrameOperation/InnerTypeConvertionOperation.h"
#include "ScriptCompiler/FrameOperation/InitValueTypeOperation.h"
#include "ScriptCompiler/FrameOperation/LoadFunctionOperation.h"
#include "ScriptCompiler/FrameOperation/DynamicCastClassOperation.h"

#include "ScriptRuntime/ByteCode/ByteCodeFile.h"
#include "ScriptRuntime/ByteCode/ByteCodeWriter.h"

#include "ScriptCompiler/CompilerContext.h"

#include "Reflection/Function.h"

// CByteCodeFile::STypeInfo CreateTypeInfo(std::shared_ptr<CTypeDescription> InClassDescription,std::shared_ptr<CCompilerContext> InCompilerContext );

CByteCodeFile::STypeInfo CreateTypeInfo(const SDecoratedTypeDescription& InTypeDescription,std::shared_ptr<CCompilerContext> InCompilerContext )
{
    CByteCodeFile::STypeInfo TypeInfo;
    TypeInfo.TypeID =InTypeDescription.BasicType->TypeID;
    for(auto& OverrideGenericArgument : InTypeDescription.OverrideGenericClasses)
    {
        TypeInfo.OverrideGenericTypes.Add(OverrideGenericArgument->TypeID);
    }

    return TypeInfo;
}


// CByteCodeFile::STypeInfo CreateTypeInfo(std::shared_ptr<CTypeDescription> InClassDescription,std::shared_ptr<CCompilerContext> InCompilerContext )
// {
//     CByteCodeFile::STypeInfo TypeInfo;
//     TypeInfo.TypeID =InClassDescription ?  InClassDescription->TypeID : STypeID::GetVoidTypeID();

//     return TypeInfo;
// }

 
void WriteLoadLiteral(std::shared_ptr<CBytecodeWriter> InWriter
    , TVector<uint32_t>& InOutLinePositions
    , std::shared_ptr<CLoadLiteralOperation> InOperation
    , std::shared_ptr<CCompilerContext> InCompilerContext)
{
    switch(InOperation->Value.Type)
    {
        case EConstantValueType::String:
        {
            InWriter->AddLoadLiteralOperation(InOperation->Value.StringValue);
            break;
        }

        case EConstantValueType::Char:
        {
            InWriter->AddLoadLiteralOperation(InOperation->Value.CharValue);
            break;
        }

        case EConstantValueType::Bool:
        {
            InWriter->AddLoadLiteralOperation(InOperation->Value.BoolValue);
            break;
        }

        case EConstantValueType::Int8:
        {
            InWriter->AddLoadLiteralOperation(InOperation->Value.Int8Value);
            break;
        }

        case EConstantValueType::Int16:
        {
            InWriter->AddLoadLiteralOperation(InOperation->Value.Int16Value);
            break;
        }

        case EConstantValueType::Int32:
        {
            InWriter->AddLoadLiteralOperation(InOperation->Value.Int32Value);
            break;
        }

        case EConstantValueType::Int64:
        {
            InWriter->AddLoadLiteralOperation(InOperation->Value.Int64Value);
            break;
        }

        case EConstantValueType::UInt8:
        {
            InWriter->AddLoadLiteralOperation(InOperation->Value.UInt8Value);
            break;
        }

        case EConstantValueType::UInt16:
        {
            InWriter->AddLoadLiteralOperation(InOperation->Value.UInt16Value);
            break;
        }

        case EConstantValueType::UInt32:
        {
            InWriter->AddLoadLiteralOperation(InOperation->Value.UInt32Value);
            break;
        }

        case EConstantValueType::UInt64:
        {
            InWriter->AddLoadLiteralOperation(InOperation->Value.UInt64Value);
            break;
        }

        case EConstantValueType::Float:
        {
            InWriter->AddLoadLiteralOperation(InOperation->Value.FloatValue);
            break;
        }

        case EConstantValueType::Double:
        {
            InWriter->AddLoadLiteralOperation(InOperation->Value.DoubleValue);
            break;
        }

        default:
        {
            assert(false && "unimplemented");
            break;
        }

    }

    //add next line position
    InOutLinePositions.Add(InWriter->GetByteCodeSize());
    return;
}

void FillByteCodeFile(std::shared_ptr<CBytecodeWriter> InWriter, TVector<uint32_t>& InOutLinePositions, std::shared_ptr<CBoxInnerTypeOperation> InOperation, std::shared_ptr<CCompilerContext> InCompilerContext)
{
    InWriter->AddBoxOperation(CreateTypeInfo(InOperation->InnerType,InCompilerContext));

    //add next line position
    InOutLinePositions.Add(InWriter->GetByteCodeSize());
}

void FillByteCodeFile(
        std::shared_ptr<CBytecodeWriter> InWriter
        , TVector<uint32_t>& InOutLinePositions
        , std::shared_ptr<IFrameOperation> InOperation
        , std::shared_ptr<CCompilerContext> InCompilerContext)
{
    auto OperationType=InOperation->GetType();
    switch(OperationType)
    {
        case EFrameOperationType::InnerTypeConvertion:
        {
            auto InnerTypeConvertionOperation= std::static_pointer_cast<CInnerTypeConvertionOperation>(InOperation);
            InWriter->AddInnerTypeConvertion(InnerTypeConvertionOperation->SourceType,InnerTypeConvertionOperation->TargetType);
            
            //add next line position
            InOutLinePositions.Add(InWriter->GetByteCodeSize());
            break;
        }

        case EFrameOperationType::Instruction :
        {
            auto InstructionOperation= std::static_pointer_cast<CSimpleInstructionOperation>(InOperation);
            InWriter->AddInstruction(InstructionOperation->GetInstruction());

            //add next line position
            InOutLinePositions.Add(InWriter->GetByteCodeSize());

            break;
        }

        case EFrameOperationType::Jump:
        {
            auto JumpOperation= std::static_pointer_cast<CJumpOperation>(InOperation);

            switch(JumpOperation->Condition)
            {
                case CJumpOperation::EJumpCondition::Always:
                {
                    //do nothing
                    InWriter->AddJumpOperation(JumpOperation->JumpTo->LineIndex,EByteCodeInstructions::Br);
                    break;
                }
                case CJumpOperation::EJumpCondition::IfFalse:
                {
                    InWriter->AddJumpOperation(JumpOperation->JumpTo->LineIndex,EByteCodeInstructions::BrFalse);
                    break;
                }
                case CJumpOperation::EJumpCondition::IfTrue:
                {
                    InWriter->AddJumpOperation(JumpOperation->JumpTo->LineIndex,EByteCodeInstructions::BrTrue);
                    break;
                }
                case CJumpOperation::EJumpCondition::IfEqual:
                {
                    InWriter->AddJumpOperation(JumpOperation->JumpTo->LineIndex,EByteCodeInstructions::BrEqual);
                    break;
                }

                default:
                    assert(false && "unimplemented");
            }

            //add next line position
            InOutLinePositions.Add(InWriter->GetByteCodeSize());

            break;
        }

        case EFrameOperationType::DoNothing:
        {
            InWriter->AddDoNothingOperation();

            //add next line position
            InOutLinePositions.Add(InWriter->GetByteCodeSize());

            break;
        }

        case EFrameOperationType::LoadLiteral:
        {
            std::shared_ptr<CLoadLiteralOperation> LoadLiteralOperation = std::static_pointer_cast<CLoadLiteralOperation>(InOperation);
            WriteLoadLiteral(InWriter,InOutLinePositions, LoadLiteralOperation, InCompilerContext);
            break;
        }

        case EFrameOperationType::StoreVariable:
        {
            std::shared_ptr<CStoreVariableOperation> StoreVariableOperation = std::static_pointer_cast<CStoreVariableOperation>(InOperation);
            InWriter->AddStoreVariableOperation(StoreVariableOperation->GetVariable()->Index);
            
            //add next line position
            InOutLinePositions.Add(InWriter->GetByteCodeSize());

            break;

        }

        case EFrameOperationType::ConstructorCall:
        {
            std::shared_ptr<CConstructorCallOperation> ConstructorCallOperation = std::static_pointer_cast<CConstructorCallOperation>(InOperation);

            auto ConstructorDescription = ConstructorCallOperation ->GetConstructorDescription();

            CByteCodeFile::SFunctionCallInfo FunCallInfo;

            FunCallInfo.Class=ConstructorDescription->ParentClass.lock()->TypeID;//   CreateTypeInfo(ConstructorDescription->ParentClass.lock(),InCompilerContext);
            FunCallInfo.Function = ConstructorDescription->FunctionIndex;

            InWriter->AddFunctionCallOperation(FunCallInfo);

            //add next line position
            InOutLinePositions.Add(InWriter->GetByteCodeSize());

            break;
        }

        case EFrameOperationType::FunctionCall:
        {
            std::shared_ptr<CFunctionCallOperation> FunctionCallOperation = std::static_pointer_cast<CFunctionCallOperation>(InOperation);
            auto FunctionDescription = FunctionCallOperation->FunctionDescription;
            CByteCodeFile::SFunctionCallInfo FunCallInfo;

            FunCallInfo.Class=FunctionDescription->ParentClass.lock()->TypeID; //CreateTypeInfo(FunctionDescription->ParentClass.lock(),InCompilerContext);
            assert(FunctionDescription->FunctionIndex != -1);
            FunCallInfo.Function = FunctionDescription->FunctionIndex;

            InWriter->AddFunctionCallOperation(FunCallInfo);

            //add next line position
            InOutLinePositions.Add(InWriter->GetByteCodeSize());

            break;
        }

        case EFrameOperationType::VirtualFunctionCall:
        {
            std::shared_ptr<CVirtualFunctionCallOperation> VirtualFunctionCallOperation = std::static_pointer_cast<CVirtualFunctionCallOperation>(InOperation);
            auto FunctionDescription = VirtualFunctionCallOperation->FunctionDescription;

            CByteCodeFile::SFunctionCallInfo FunCallInfo;

            FunCallInfo.Class=FunctionDescription->ParentClass.lock()->TypeID;   //CreateTypeInfo(FunctionDescription->ParentClass.lock(),InCompilerContext);
            FunCallInfo.Function =FunctionDescription->PositionInVirtualTable;

            InWriter->AddVirtualFunctionCallOperation(FunCallInfo);

            //add next line position
            InOutLinePositions.Add(InWriter->GetByteCodeSize());

            break;
        }

        case EFrameOperationType::LoadArgument:
        {
            std::shared_ptr<CLoadArgumentOperation> LoadArgumentOperation = std::static_pointer_cast<CLoadArgumentOperation>(InOperation);
            InWriter->AddLoadArgumentOperation(LoadArgumentOperation->GetParameterIndex());

            //add next line position
            InOutLinePositions.Add(InWriter->GetByteCodeSize());

            break;
        }

        case EFrameOperationType::LoadArgumentAddress:
        {
            std::shared_ptr<CLoadArgumentAddressOperation> LoadArgumentAddressOperation = std::static_pointer_cast<CLoadArgumentAddressOperation>(InOperation);
            InWriter->AddLoadArgumentAddressOperation(LoadArgumentAddressOperation->GetParameterIndex());

            //add next line position
            InOutLinePositions.Add(InWriter->GetByteCodeSize());

            break;
        }

        case EFrameOperationType::StoreArgument:
        {
            std::shared_ptr<CStoreArgumentOperation> StoreArgumentOperation = std::static_pointer_cast<CStoreArgumentOperation>(InOperation);
            InWriter->AddStoreArgumentOperation(StoreArgumentOperation->GetParameterIndex());

            //add next line position
            InOutLinePositions.Add(InWriter->GetByteCodeSize());
            break;
        }

        case EFrameOperationType::BoxInnerType:
        {
            std::shared_ptr<CBoxInnerTypeOperation> BoxInnerTypeOperation = std::static_pointer_cast<CBoxInnerTypeOperation>(InOperation);
            FillByteCodeFile(InWriter,InOutLinePositions, BoxInnerTypeOperation, InCompilerContext);
            break;
        }

        case EFrameOperationType::LoadVariable:
        {
            std::shared_ptr<CLoadVariableOperation> LoadVariableOperation = std::static_pointer_cast<CLoadVariableOperation>(InOperation);
            InWriter->AddLoadVariableOperation(LoadVariableOperation->GetVariable()->Index);
            
            //add next line position
            InOutLinePositions.Add(InWriter->GetByteCodeSize());
            
            break;
        }

        case EFrameOperationType::LoadVariableAddress:
        {
            std::shared_ptr<CLoadVariableAddressOperation> LoadVariableAddressOperation = std::static_pointer_cast<CLoadVariableAddressOperation>(InOperation);
            InWriter->AddLoadVariableAddressOperation(LoadVariableAddressOperation->GetVariable()->Index);
            
            //add next line position
            InOutLinePositions.Add(InWriter->GetByteCodeSize());
            
            break;
        }

        case EFrameOperationType::StoreField:
        {
            
            std::shared_ptr<CStoreFieldOperation> StoreFieldOperation = std::static_pointer_cast<CStoreFieldOperation>(InOperation);
            if(StoreFieldOperation->FieldDescription->Decorator->bIsStatic)
            {
                InWriter->AddStoreStaticFieldOperation(
                    StoreFieldOperation->FieldDescription->ParentClass.lock()->TypeID
                    ,StoreFieldOperation->FieldDescription->FieldIndex);
            }
            else
            {
                InWriter->AddStoreFieldOperation(StoreFieldOperation->FieldDescription->FieldIndex);
            }

            //add next line position
            InOutLinePositions.Add(InWriter->GetByteCodeSize());
            
            break;
        }

        case EFrameOperationType::LoadField:
        {
            std::shared_ptr<CLoadFieldOperation> LoadFieldOperation = std::static_pointer_cast<CLoadFieldOperation>(InOperation);
            if(LoadFieldOperation->FieldDescription->Decorator->bIsStatic)
            {
                InWriter->AddLoadStaticFieldOperation(
                    LoadFieldOperation->FieldDescription->ParentClass.lock()->TypeID
                    ,LoadFieldOperation->FieldDescription->FieldIndex);
            }
            else
            {
                InWriter->AddLoadFieldOperation(LoadFieldOperation->FieldDescription->FieldIndex);
            }
            //add next line position
            InOutLinePositions.Add(InWriter->GetByteCodeSize());
            break;
        }

        case EFrameOperationType::LoadFieldAddress:
        {
            std::shared_ptr<CLoadFieldAddressOperation> LoadFieldOperation = std::static_pointer_cast<CLoadFieldAddressOperation>(InOperation);
            if(LoadFieldOperation->FieldDescription->Decorator->bIsStatic)
            {
                InWriter->AddLoadStaticFieldAddressOperation(
                    LoadFieldOperation->FieldDescription->ParentClass.lock()->TypeID
                    ,LoadFieldOperation->FieldDescription->FieldIndex);
            }
            else
            {
                InWriter->AddLoadFieldAddressOperation(LoadFieldOperation->FieldDescription->FieldIndex);
            }
            //add next line position
            InOutLinePositions.Add(InWriter->GetByteCodeSize());
            break;            
        }

        case EFrameOperationType::NewObject:
        {
            std::shared_ptr<CNewObjectOperation> NewObjectOperation = std::static_pointer_cast<CNewObjectOperation>(InOperation);
            
            CByteCodeFile::SFunctionCallInfo ConstructorCallInfo;
            ConstructorCallInfo.Class =NewObjectOperation->ClassDeclaration->TypeID;//  CreateTypeInfo(NewObjectOperation->ClassDeclaration , InCompilerContext);
            ConstructorCallInfo.Function = 
                NewObjectOperation->ConstructorFunction 
                ? NewObjectOperation->ConstructorFunction->FunctionIndex 
                : -1;

            assert(NewObjectOperation->ClassDeclaration->IsClass());
            InWriter->AddNewObjectOperation(ConstructorCallInfo);

            //add next line position
            InOutLinePositions.Add(InWriter->GetByteCodeSize());
            break;
        }
        
        case EFrameOperationType::InitValueType:
        {
            std::shared_ptr<CInitValueTypeOperation> InitObjectOperation = std::static_pointer_cast<CInitValueTypeOperation>(InOperation);

            InWriter->AddInitValueTypeOperation(InitObjectOperation->GetClassDescription()->TypeID);

            //add next line position
            InOutLinePositions.Add(InWriter->GetByteCodeSize());
            break;
        }

        case EFrameOperationType::Return:
        {
            InWriter->AddReturnOperation();\
            
            //add next line position
            InOutLinePositions.Add(InWriter->GetByteCodeSize());

            break;
        }

        case EFrameOperationType::LoadFunction:
        {
            std::shared_ptr<CLoadFunctionOperation> LoadFunctionOperation = std::static_pointer_cast<CLoadFunctionOperation>(InOperation);

            CByteCodeFile::SFunctionLoadInfo FunctionLoadInfo;
            FunctionLoadInfo.Class =LoadFunctionOperation->GetFunctionDescription()->ParentClass.lock()->TypeID;//  CreateTypeInfo(LoadFunctionOperation->GetFunctionDescription()->ParentClass.lock(), InCompilerContext);
            FunctionLoadInfo.Function = LoadFunctionOperation->GetFunctionDescription()->FunctionIndex; 

            InWriter->AddLoadFunctionOperation(FunctionLoadInfo);

            //add next line position
            InOutLinePositions.Add(InWriter->GetByteCodeSize());

            break;

        }

        case EFrameOperationType::CastClass:
        {
            std::shared_ptr<CDynamicCastClassOperation> CastClassOperation = std::static_pointer_cast<CDynamicCastClassOperation>(InOperation);

            InWriter->AddCastClassOperation(CastClassOperation->GetTargetClassDescription()->TypeID);

            //add next line position
            InOutLinePositions.Add(InWriter->GetByteCodeSize());

            break;
        }

        default :
        {
            assert(false&& U"Unimplemented operation type");
            break;
        }
    }

}

std::shared_ptr<CByteCodeFile> CByteCodeGenerator::CreateByteCodeFile(std::shared_ptr<CCompilerContext> InCompilerContext)
{

    AssignNamespaceMemberIndex(InCompilerContext->GlobalNamespace,InCompilerContext);

    std::shared_ptr<CByteCodeFile> ByteCodeFile = std::make_shared<CByteCodeFile>();
    std::shared_ptr<CBytecodeWriter> Writer= std::make_shared<CBytecodeWriter>(ByteCodeFile);

    //fill file table
    for(auto& File: InCompilerContext->ScriptFiles)
    {
        CByteCodeFile::SSourceCodeFile SourceCodeFile;
        SourceCodeFile.FilePath=File.Value->FilePath;
        SourceCodeFile.LineCount=File.Value->GetLineCount();
        File.Value->FileIndex=Writer->SourceCodeFileTable.Num();
        Writer->SourceCodeFileTable.Add(SourceCodeFile);
    }

    //FillByteCodeFile(ByteCodeFile,InCompilerContext->GlobalNamespace,InCompilerContext,true);
    WriteNamespace(Writer,InCompilerContext->GlobalNamespace,InCompilerContext,true);

    WriteClasses(Writer,InCompilerContext);

    WriteClassesContent(Writer,InCompilerContext);

    Writer->Finish();

    return ByteCodeFile;
}

void CByteCodeGenerator::AssignNamespaceMemberIndex(std::shared_ptr<CNamespaceDescription> InNamespace, std::shared_ptr<CCompilerContext> InCompilerContext)
{

    for(auto& Class : InNamespace->Classes)
    {
        AssignMemberFieldIndex(Class,InCompilerContext);
        AssignMemberFunctionIndex(Class,InCompilerContext);

    }

    for(auto& Namespace :InNamespace->Namespaces)
    {
        AssignNamespaceMemberIndex(Namespace,InCompilerContext);
    }

}

void CByteCodeGenerator::AssignMemberFieldIndex(std::shared_ptr<CTypeDescription> InClass,std::shared_ptr<CCompilerContext> InCompilerContext)
{
    //donot assign index to native class
    //native class index is assigned in derivation stage
    if(InClass->IsNative())
    {
        return ;
    }

    TVector<std::shared_ptr<CTypeDescription>> AllClasses={InClass};
    {
        std::shared_ptr<CTypeDescription> CurrentClass = InClass;

        while(CurrentClass->BaseClass
            && !CurrentClass->BaseClass->IsNative() //native fields already combined parent class fields
        )
        {
            AllClasses.Add(CurrentClass->BaseClass);
            CurrentClass = CurrentClass->BaseClass;
        }
    }

    
    int FieldIndex=0;
    if(AllClasses.Last()->BaseClass
        && AllClasses.Last()->BaseClass->IsNative()
        && AllClasses.Last()->BaseClass->IsClass()
    )
    {
        FieldIndex=AllClasses.Last()->BaseClass->SubclassNonStaticFieldIndexStart;
        assert(FieldIndex != -1 && "native class must have field index start");
    }

    int StaticFieldIndex=0;
    for(int i=AllClasses.Num()-1;i>=0;i--)
    {
        int FunctionIndex=0;
        auto& Class = AllClasses[i];

        for(auto& Field : Class->Fields)
        {
            if(Field->IsStatic())
            {
                Field->FieldIndex = StaticFieldIndex;
                StaticFieldIndex++;
            }
            else
            {
                Field->FieldIndex = FieldIndex;
                FieldIndex++;
            }
        }

    }

    if(InClass->IsGeneric())
    {
        for(auto& Placeholder: InClass->GenericPlaceholders)
        {
            AssignMemberFieldIndex(Placeholder,InCompilerContext);
        }

        for(auto& Generalized : InClass->GeneralizedClasses)
        {
            AssignMemberFieldIndex(Generalized.Value,InCompilerContext);
        }
    }

    for(auto& SubClass : InClass->Classes)
    {
        AssignMemberFieldIndex(SubClass,InCompilerContext);
    }
}

void CByteCodeGenerator::AssignMemberFunctionIndex(std::shared_ptr<CTypeDescription> InClass, std::shared_ptr<CCompilerContext> InCompilerContext)
{
    int FunctionIndex=0;
    for(auto& Constructor : InClass->Constructors)
    {
        Constructor->FunctionIndex = FunctionIndex;
        FunctionIndex++;
    }  

    for(auto& Function: InClass->Functions)
    {
        Function->FunctionIndex = FunctionIndex;
        FunctionIndex++;
    }  

    if(InClass->IsGeneric())
    {
        for(auto& Placeholder: InClass->GenericPlaceholders)
        {
            AssignMemberFunctionIndex(Placeholder,InCompilerContext);
        }

        for(auto& Generalized : InClass->GeneralizedClasses)
        {
            AssignMemberFunctionIndex(Generalized.Value,InCompilerContext);
        }
    }

    for(auto& SubClass : InClass->Classes)
    {
        AssignMemberFunctionIndex(SubClass,InCompilerContext);
    }    
}

void CByteCodeGenerator::WriteNamespace(std::shared_ptr<CBytecodeWriter> InWriter, std::shared_ptr<CNamespaceDescription> InNamespace, std::shared_ptr<CCompilerContext> InCompilerContext, bool bIsGlobalNamespace)
{
    uint32_t NamespaceIndex = 0;
    if(!bIsGlobalNamespace)
    {
        auto ParentNamespaceID= InNamespace->FullNameChain;
        ParentNamespaceID.RemoveAt(ParentNamespaceID.Num()-1);

        NamespaceIndex=InWriter->BeginNamespace(
            InNamespace->GetSearchName()
            ,ParentNamespaceID
            );
    }

    for(auto SubNamespace : InNamespace->Namespaces)
    {
        WriteNamespace(InWriter,SubNamespace,InCompilerContext,false);
    }

    if(!bIsGlobalNamespace)
    {
        InWriter->EndNamespace(NamespaceIndex);
    }

}

void CByteCodeGenerator::WriteClasses(std::shared_ptr<CBytecodeWriter> InWriter, std::shared_ptr<CCompilerContext> InCompilerContext)
{
    for(auto& Class : InCompilerContext->ScriptClasses)
    {
        WriteClass(InWriter,Class,InCompilerContext);
    }

}

void CByteCodeGenerator::WriteClass(std::shared_ptr<CBytecodeWriter> InWriter, std::shared_ptr<CTypeDescription> InClass, std::shared_ptr<CCompilerContext> InCompilerContext)
{
    if(InClass->IsInnerType()) //skip void type
    {
        return ; 
    }

    if(InClass->IsGenerailizedClass())
    {
        CByteCodeFile::SGeneralizedClasseInfo GeneralizedInfo;
        GeneralizedInfo.TypeID= InClass->TypeID;

        if(InClass->RawClassTypeID.HasValue())
        {
            GeneralizedInfo.RawClassTypeID=InClass->RawClassTypeID.GetValue();
        }

        if(InClass->BaseClass)
        {
            if(InClass->BaseClass->RawClassTypeID.HasValue())
            {
                GeneralizedInfo.BaseTypeID=InClass->BaseClass->RawClassTypeID.GetValue();
            }
            else
            {
                GeneralizedInfo.BaseTypeID= InClass->BaseClass->TypeID;
            }
        }

        GeneralizedInfo.Name= InClass->Name;
        if(InClass->GenericClass->RawClassTypeID.HasValue())
        {
            GeneralizedInfo.TemplateID=InClass->GenericClass->RawClassTypeID.GetValue();
        }
        else
        {
            GeneralizedInfo.TemplateID= InClass->GenericClass->TypeID;

        }

        GeneralizedInfo.ArgumentInfos.Reserve(InClass->GenericArguments.Arguments.Num());
        for(auto GenericArgument : InClass->GenericArguments.Arguments)
        {
            GeneralizedInfo.ArgumentInfos.Add(CreateTypeInfo(GenericArgument,InCompilerContext));
        }

        InWriter->AddGeneralizedClass(GeneralizedInfo);
        return ;
    }

    if(InClass->IsFunctionPointer())
    {
        CByteCodeFile::SFunctionType FunctionType;
        FunctionType.TypeID = InClass->TypeID;
        FunctionType.Name= InClass->Name;
        FunctionType.ReturnType = CreateTypeInfo(InClass->FunctionPointerReturnType,InCompilerContext);
        for(auto& Argument : InClass->FunctionPointerParameters)
        {
            FunctionType.ParameterTypes.Add(CreateTypeInfo(Argument,InCompilerContext));
        }
        InWriter->AddFunctionType(FunctionType);
        return ;
    }

    CByteCodeFile::SClassInfo ClassInfo;
    ClassInfo.AccessLevel = EScriptAccessLevel::Public;
    ClassInfo.bIsInnerType = InClass->IsInnerType();
    ClassInfo.Name = InClass->GetSearchName();
    ClassInfo.NameScopeID = InClass->ParentScopeNameChain;

    ClassInfo.TypeID = InClass->TypeID;
    if(InClass->RawClassTypeID.HasValue())
    {
        ClassInfo.RawClassTypeID = InClass->RawClassTypeID.GetValue();
    }

    if(InClass->IsClass())
    {
        assert(InClass->BaseClass->RawClassTypeID.HasValue());
        ClassInfo.BaseTypeID = InClass->BaseClass->RawClassTypeID.GetValue();
    }

    if(InClass->IsStruct() )
    {
        ClassInfo.bIsStruct = true;
    }
    
    if(InClass->IsDelegate())
    {
        ClassInfo.bIsDelegate = true;
        ClassInfo.DelegateFunctionType=
            InClass->DelegateFunctionPointerType.BasicType->TypeID;
    }

    if(InClass->IsEnum())
    {
        ClassInfo.bIsEnum = true;
    }

    //add generic place holder
    for(auto GenericPlaceholder : InClass->GenericPlaceholders)
    {
        CByteCodeFile::SGenericPlaceholderTypeInfo PlaceholderInfo;
        PlaceholderInfo.Name = GenericPlaceholder->GetSearchName();
        PlaceholderInfo.TypeID = GenericPlaceholder->TypeID;
        ClassInfo.GenericPlaceholders.Add(PlaceholderInfo);
    }

    auto ClassIndex=InWriter->BeginClass(ClassInfo);

    InWriter->EndClass(ClassIndex);    
}

void CByteCodeGenerator::WriteClassesContent(std::shared_ptr<CBytecodeWriter> InWriter, std::shared_ptr<CCompilerContext> InCompilerContext)
{
    for(auto Class : InCompilerContext->ScriptClasses)
    {
        WriteClassContent(InWriter,Class,InCompilerContext);
    }
}

void CByteCodeGenerator::WriteClassContent(std::shared_ptr<CBytecodeWriter> InWriter, std::shared_ptr<CTypeDescription> InClass, std::shared_ptr<CCompilerContext> InCompilerContext)
{
    if(InClass->IsInnerType()) //skip void type
    {
        return ;
    }

    //if template is native , skip it
    //because native class is already written a gnerialize method
    if(InClass->IsGenerailizedClass()
        && InClass->GenericClass->IsNative()
    )
    {
        return ;
    }

    if(InClass->IsFunctionPointer())
    {
        return ;
    }

    if(InClass->IsDelegate())
    {
        return;
    }


    auto ContentStart= InWriter->BeginClassContent(InClass->TypeID);

    //add vitual function table at head of class
    TVector<SFunctionID> VirtualFunctionTable;
    for(auto Function : InClass->VirtualFunctionTable)
    {
        VirtualFunctionTable.Add(Function->GetFunctionID());
    }
    InWriter->AddVirtualFunctionTable(VirtualFunctionTable);


    for(auto Field : InClass->Fields)
    {
        CByteCodeFile::SFieldInfo FieldInfo;
        FieldInfo.AccessLevel = EScriptAccessLevel::Public;
        FieldInfo.Name = Field->GetSearchName();
        FieldInfo.Type=CreateTypeInfo(Field->Type, InCompilerContext);
        FieldInfo.FieldIndex = Field->FieldIndex;
        FieldInfo.Decorator.bIsStatic = Field->IsStatic();
        if(Field->ConstValue.HasValue())
        {
            FieldInfo.DefaultValue=Field->ConstValue.GetValue();
        }

        InWriter->AddField(FieldInfo);
    }

    for(auto& Constructor :InClass->Constructors)
    {
        CByteCodeFile::SFunctionInfo FunctionInfo;
        FunctionInfo.AccessLevel = EScriptAccessLevel::Public;
        FunctionInfo.Name = CFunction::GetConstructorName();
        FunctionInfo.bIsStatic = Constructor->bIsStatic;

        for(auto Parameter : Constructor->Parameters)
        {
            CString ParameterName = Parameter->Name;
            CByteCodeFile::STypeInfo Type;
            Type=CreateTypeInfo(Parameter->Type,InCompilerContext);

            CByteCodeFile::SVariableInfo ParameterInfo;
            ParameterInfo.Name = ParameterName;
            ParameterInfo.Type = Type;

            FunctionInfo.Parameters.Add(ParameterInfo);
        }

        FunctionInfo.ReturnType.TypeID=CGetType<void>()()->ID;

        auto FunctionByteIndex=InWriter->BeginFunction();

        for(auto& LocalVariable:  Constructor->Body.GetValue()->LocalVariables)
        {
            CByteCodeFile::SVariableInfo VariableInfo;
            VariableInfo.Name = LocalVariable->Name;
            VariableInfo.Type = CreateTypeInfo(LocalVariable->Type,InCompilerContext);

            FunctionInfo.VariableStack.Add(VariableInfo);

        }


        //add next line position
        TVector<uint32_t> LinePositions;
        LinePositions.Add(InWriter->GetByteCodeSize());

        //uint32_t OperationIndex=0;
        for(auto Operation :  Constructor->Body.GetValue()->GetOperations())
        {
            FillByteCodeFile(InWriter,LinePositions,Operation,InCompilerContext);
            
            if(Operation->IsBreakable())
            {
                CByteCodeFile::SBreakpointInfo BreakpointInfo;
                BreakpointInfo.FileIndex= Operation->File.GetValue()->FileIndex;
                BreakpointInfo.SourceCodeLineNumber= Operation->FileLineIndex.GetValue();
                BreakpointInfo.InstructionIndex=//OperationIndex;
                    LinePositions.Num()-1;
                FunctionInfo.BreakablePoints.Add(BreakpointInfo);

            }

            //OperationIndex++;

        }
        LinePositions.RemoveAt(LinePositions.Num() - 1);

        FunctionInfo.LinePositions.Reserve(LinePositions.Num());
        for(auto LineAbsolutePosition : LinePositions)
        {
            FunctionInfo.LinePositions.Add(LineAbsolutePosition-LinePositions[0]);
        }

        InWriter->EndFunction(FunctionByteIndex,FunctionInfo);
    }

    for(auto Function : InClass->Functions)
    {
        CByteCodeFile::SFunctionInfo FunctionInfo;
        FunctionInfo.AccessLevel = EScriptAccessLevel::Public;
        FunctionInfo.Name = Function->GetSearchName();

        FunctionInfo.bIsStatic = Function->IsStatic();
        
        for(auto Parameter : Function->Parameters)
        {
            CString ParameterName = Parameter->Name;
            CByteCodeFile::STypeInfo Type;
            Type=CreateTypeInfo(Parameter->Type,InCompilerContext);

            CByteCodeFile::SVariableInfo ParameterInfo;
            ParameterInfo.Name = ParameterName;
            ParameterInfo.Type = Type;

            FunctionInfo.Parameters.Add(ParameterInfo);
        }

        FunctionInfo.ReturnType=CreateTypeInfo(Function->ReturnType,InCompilerContext);
        auto FunctionByteIndex=InWriter->BeginFunction();

        uint32_t LocalVariableIndex = 0;
        uint32_t LineIndex=0;

        if(Function->Body.HasValue())
        {     
            for(auto& LocalVariable: Function->Body.GetValue()->LocalVariables)
            {
                CByteCodeFile::SVariableInfo VariableInfo;
                VariableInfo.Name = LocalVariable->Name;
                VariableInfo.Type = CreateTypeInfo(LocalVariable->Type,InCompilerContext);

                FunctionInfo.VariableStack.Add(VariableInfo);
                LocalVariableIndex++;
            }

            for(auto Operation : Function->Body.GetValue()->GetOperations())
            {
                Operation->LineIndex=LineIndex;
                LineIndex++;
            }
        }
        
        //add next line position
        TVector<uint32_t> LinePositions;
        if(Function->Body.HasValue())
        {
            LinePositions.Add(InWriter->GetByteCodeSize());

            //uint32_t OperationIndex=0;
            for(auto Operation : Function->Body.GetValue()->GetOperations())
            {
                FillByteCodeFile(InWriter,LinePositions,Operation,InCompilerContext);
                
                if(Operation->IsBreakable())
                {
                    CByteCodeFile::SBreakpointInfo BreakpointInfo;
                    BreakpointInfo.FileIndex= Operation->File.GetValue()->FileIndex;
                    BreakpointInfo.SourceCodeLineNumber= Operation->FileLineIndex.GetValue();
                    BreakpointInfo.InstructionIndex= //OperationIndex;
                        LinePositions.Num()-1;
                    FunctionInfo.BreakablePoints.Add(BreakpointInfo);

                }

                //OperationIndex++;

            }
            LinePositions.RemoveAt(LinePositions.Num() - 1);
        }

        FunctionInfo.LinePositions.Reserve(LinePositions.Num());
        for(auto LineAbsolutePosition : LinePositions)
        {
            FunctionInfo.LinePositions.Add(LineAbsolutePosition-LinePositions[0]);
        }

        InWriter->EndFunction(FunctionByteIndex,FunctionInfo);
        
    }

    InWriter->EndClassContent(ContentStart);
}
